/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/


/********************************************************************************
 screen shot tool by Chris Muelder (cwmuelder@ucdavis.edu)
 ********************************************************************************/
 
#include "screenShotTool.h"
#include "controller.h"
#include <assert.h>
#include <sstream>

#ifdef USING_LIBPNG
#include <png.h>
#define abort_(X) {fprintf(stderr, X); return;}
//#define abort_(X,Y) {fprintf(stderr, X,Y); return;}
#endif
#include "GL/glew.h"
#include "glheaders.h"
#include "viewManager.h"

extern int currentFontSize;

using namespace std;

void SavePNG(std::string fname)
{
int x, y;

//int width=GetWidth(), height=GetHeight();
int vp[4];
glGetIntegerv(GL_VIEWPORT, vp);

int width, height;
width=vp[2]-vp[0];
height=vp[3]-vp[1];

png_byte color_type=PNG_COLOR_TYPE_RGB;
png_byte bit_depth=8;

png_structp png_ptr;
png_infop info_ptr;
int number_of_passes;
png_bytep * row_pointers;

#ifdef USING_LIBPNG
        /* create file */
        FILE *fp = fopen(fname.c_str(), "wb");
        if (!fp)
                abort_("[write_png_file] File could not be opened for writing");


        /* initialize stuff */
        png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

        if (!png_ptr)
                abort_("[write_png_file] png_create_write_struct failed");

        info_ptr = png_create_info_struct(png_ptr);
        if (!info_ptr)
                abort_("[write_png_file] png_create_info_struct failed");

        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during init_io");

        png_init_io(png_ptr, fp);


        /* render and read scene*/
        //Draw();
        
        row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
        for (y=0; y<height; y++)
        {
          row_pointers[y] = (png_byte*) malloc(3*width*sizeof(png_byte));
          glReadPixels(0, height-1-y, width, 1, GL_RGB, GL_UNSIGNED_BYTE, (void *)row_pointers[y]);
        }

        /* write header */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during writing header");

        png_set_IHDR(png_ptr, info_ptr, width, height,
                     bit_depth, color_type, PNG_INTERLACE_NONE,
                     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

        png_write_info(png_ptr, info_ptr);


        /* write bytes */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during writing bytes");

        png_write_image(png_ptr, row_pointers);


        /* end write */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during end of write");

        png_write_end(png_ptr, NULL);

        /* cleanup heap allocation */
        for (y=0; y<height; y++)
                free(row_pointers[y]);
        free(row_pointers);

        fclose(fp);
#endif __LIB_PNG__

}
void SavePNGTex(std::string fname, GLint texid, int width, int height)
{
int x, y;


png_byte color_type=PNG_COLOR_TYPE_RGB;
png_byte bit_depth=8;

png_structp png_ptr;
png_infop info_ptr;
int number_of_passes;
png_bytep * row_pointers;

#ifdef USING_LIBPNG
        /* create file */
        FILE *fp = fopen(fname.c_str(), "wb");
        if (!fp)
                abort_("[write_png_file] File could not be opened for writing");


        /* initialize stuff */
        png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

        if (!png_ptr)
                abort_("[write_png_file] png_create_write_struct failed");

        info_ptr = png_create_info_struct(png_ptr);
        if (!info_ptr)
                abort_("[write_png_file] png_create_info_struct failed");

        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during init_io");

        png_init_io(png_ptr, fp);


        /* read scene*/
				glBindTexture(GL_TEXTURE_2D, texid);
				unsigned char * raw_img_data = new unsigned char[width*height*4];
				glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, raw_img_data); 
        
        row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
        for (y=0; y<height; y++)
        {
          row_pointers[y] = (png_byte*) malloc(3*width*sizeof(png_byte));
          //glReadPixels(0, height-1-y, width, 1, GL_RGB, GL_UNSIGNED_BYTE, (void *)row_pointers[y]);
					for(x=0;x<width;x++)
					{
						row_pointers[y][3*x+0]=raw_img_data[4*(height-1-y)*width+4*x+0];
						row_pointers[y][3*x+1]=raw_img_data[4*(height-1-y)*width+4*x+1];
						row_pointers[y][3*x+2]=raw_img_data[4*(height-1-y)*width+4*x+2];
					}
        }
				delete raw_img_data;

        /* write header */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during writing header");

        png_set_IHDR(png_ptr, info_ptr, width, height,
                     bit_depth, color_type, PNG_INTERLACE_NONE,
                     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

        png_write_info(png_ptr, info_ptr);


        /* write bytes */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during writing bytes");

        png_write_image(png_ptr, row_pointers);


        /* end write */
        if (setjmp(png_jmpbuf(png_ptr)))
                abort_("[write_png_file] Error during end of write");

        png_write_end(png_ptr, NULL);

        /* cleanup heap allocation */
        for (y=0; y<height; y++)
                free(row_pointers[y]);
        free(row_pointers);

        fclose(fp);
#endif __LIB_PNG__

}


class ScreenShotTool:public AnalysisTool {
  GLuint fbo;
  GLuint depthBuffer;
  GLuint img;
	unsigned int w,h;
public:
	ScreenShotTool(Controller *controller):AnalysisTool("Screen","Export Screenshot",controller) {
		setPropertyString("File", "output.png");
		setPropertyInt("Image width", 1600);
		setPropertyInt("Image height", 1200);
	}
	void InitFBOTex()
	{
    //set up FBO
    glGenFramebuffersEXT(1, &fbo);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

    // Create the render buffer for depth
    glGenRenderbuffersEXT(1, &depthBuffer);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h);

    // Setup texture to render to
    glGenTextures(1, &img);
    glBindTexture(GL_TEXTURE_2D, img);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,  w, h, 0, GL_RGBA, GL_UNSIGNED_INT, NULL);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    // And attach it to the FBO so we can render to it
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

    // Attach the depth render buffer to the FBO as it's depth attachment
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);


    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        fprintf(stderr, "FBO Error %d\n", (int)status);
        //    exit(1);
    }

  }
	void deleteFBO()
	{
	  glDeleteTextures(1, &img);
    glDeleteFramebuffersEXT(1, &fbo);
    glDeleteRenderbuffersEXT(1, &depthBuffer);
  
	}
	void compute() {
		assert(controller);
		string file = getPropertyString("File");
		ViewManager * vm = controller->getViewManager();
		if(listener) listener->setProgress(0);
		
		//store current res
		int vp[4];
		glGetIntegerv(GL_VIEWPORT, vp);
		int oldwidth, oldheight;
		oldwidth=vp[2]-vp[0];
		oldheight=vp[3]-vp[1];

		//get resolution
		w=getPropertyInt("Image width");
		h=getPropertyInt("Image height");

		//init FBO
		glewInit();
		InitFBOTex();

		//render
		vm->resize(w,h);
		vm->render();
		
		//save image to file
		SavePNGTex(file,img,w,h);
		
		//cleanup
		vm->resize(oldwidth,oldheight);
		deleteFBO();
		if(listener) listener->done();
	}
};

class MultiSubScreenShotTool:public AnalysisTool {
  GLuint fbo;
  GLuint depthBuffer;
  GLuint img;
	unsigned int w,h;
public:
	MultiSubScreenShotTool(Controller *controller):AnalysisTool("Screen","Export Tiled Screenshots",controller) {
		setPropertyString("File", "output-%.3d-%.3d.png");
		setPropertyInt("Image width", 1000);
		setPropertyInt("Image height", 1000);
		setPropertyInt("Num Tiles", 4);
	}
	void InitFBOTex()
	{
    //set up FBO
    glGenFramebuffersEXT(1, &fbo);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

    // Create the render buffer for depth
    glGenRenderbuffersEXT(1, &depthBuffer);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBuffer);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h);

    // Setup texture to render to
    glGenTextures(1, &img);
    glBindTexture(GL_TEXTURE_2D, img);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,  w, h, 0, GL_RGBA, GL_UNSIGNED_INT, NULL);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    // And attach it to the FBO so we can render to it
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, img, 0);

    // Attach the depth render buffer to the FBO as it's depth attachment
    glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBuffer);


    GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    if(status != GL_FRAMEBUFFER_COMPLETE_EXT)
    {
        fprintf(stderr, "FBO Error %d\n", (int)status);
        //    exit(1);
    }

  }
	void deleteFBO()
	{
	  glDeleteTextures(1, &img);
    glDeleteFramebuffersEXT(1, &fbo);
    glDeleteRenderbuffersEXT(1, &depthBuffer);
  
	}
	
	void resizeSubScreen(int w, int h, int i, int j, int n) {
		glViewport(0,0,w,h);
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		float a = (float) w/(float) h;
		gluOrtho2D(-a+2*a*(i)/n, -a+2*a*(i+1)/n, -1.0+2.0*j/n, -1.0+2.0*(j+1.0)/n);
	
	}
	void compute() {
		int i, j, n;
		assert(controller);
		string file = getPropertyString("File");
		ViewManager * vm = controller->getViewManager();
		if(listener) listener->setProgress(0);
		//store current res
		int vp[4];
		glGetIntegerv(GL_VIEWPORT, vp);
		int oldwidth, oldheight;
		oldwidth=vp[2]-vp[0];
		oldheight=vp[3]-vp[1];
		int oldFontSize=currentFontSize;
		
		//get resolution
		w=getPropertyInt("Image width");
		h=getPropertyInt("Image height");
		n=getPropertyInt("Num Tiles");
		currentFontSize=n*currentFontSize;
		
		//init FBO
		glewInit();
		InitFBOTex();
		
		//render tiles to file
		char tilename[1024];
		for(i=0;i<n;i++)
		  for(j=0;j<n;j++)
		{
			if(listener) listener->setProgress(i*1.0/n+j*1.0/n/n);
			//render
			resizeSubScreen(w,h,i,j,n);
			vm->render();
					
			//save image to file
			sprintf(tilename, file.c_str(), i, n-1-j);
			SavePNGTex(tilename,img,w,h);
			
		}
		//cleanup
		vm->resize(oldwidth,oldheight);
		deleteFBO();
		currentFontSize=oldFontSize;
		if(listener) listener->done();
	}
};

void addScreenShotTools(Controller *controller) {
	controller->addTool(new ScreenShotTool(controller));
	controller->addTool(new MultiSubScreenShotTool(controller));
}
